]> git.r.bdr.sh - rbdr/map/blame - Map/Presentation/Base Components/MapTextEditor.swift
Update to support notes + new style
[rbdr/map] / Map / Presentation / Base Components / MapTextEditor.swift
CommitLineData
5e8ff485
RBR
1import Cocoa
2import SwiftUI
3
4class MapTextEditorController: NSViewController {
5
6 @Binding var text: String
fdb4633d 7 let onChange: () -> Void
5e8ff485 8
77d0155b
RBR
9 private let vertexRegex = MapParsingPatterns.vertex
10 private let edgeRegex = MapParsingPatterns.edge
11 private let blockerRegex = MapParsingPatterns.blocker
12 private let opportunityRegex = MapParsingPatterns.opportunity
fdb4633d 13 private let noteRegex = MapParsingPatterns.note
77d0155b
RBR
14 private let stageRegex = MapParsingPatterns.stage
15
fdb4633d 16 private let changeDebouncer: Debouncer = Debouncer(seconds: 1)
77d0155b 17
fdb4633d 18 init(text: Binding<String>, onChange: @escaping () -> Void) {
5e8ff485 19 self._text = text
fdb4633d 20 self.onChange = onChange
5e8ff485
RBR
21 super.init(nibName: nil, bundle: nil)
22 }
23
24 required init?(coder: NSCoder) {
25 fatalError("init(coder:) has not been implemented")
26 }
27
28 override func loadView() {
29 let scrollView = NSTextView.scrollableTextView()
30 let textView = scrollView.documentView as! NSTextView
31
32 scrollView.translatesAutoresizingMaskIntoConstraints = false
33
75a0e450 34 textView.allowsUndo = true
5e8ff485 35 textView.delegate = self
77d0155b 36 textView.textStorage?.delegate = self
5e8ff485
RBR
37 textView.string = self.text
38 textView.isEditable = true
39 textView.font = .monospacedSystemFont(ofSize: 16.0, weight: .regular)
40 self.view = scrollView
41 }
42
43 override func viewDidAppear() {
44 self.view.window?.makeFirstResponder(self.view)
45 }
46}
47
48extension MapTextEditorController: NSTextViewDelegate {
49
50 func textDidChange(_ obj: Notification) {
51 if let textField = obj.object as? NSTextView {
52 self.text = textField.string
fdb4633d
RBR
53
54
55 changeDebouncer.debounce {
56 DispatchQueue.main.async {
57 self.onChange()
58 }
59 }
5e8ff485
RBR
60 }
61 }
62
63 func textView(_ view: NSTextView, shouldChangeTextIn: NSRange, replacementString: String?) -> Bool
64 {
65 let range = Range(shouldChangeTextIn, in: view.string)
66 let target = view.string[range!]
67
68 if target == "--" {
69 return false
70 }
71
72 return true
73 }
74}
75
77d0155b 76extension MapTextEditorController: NSTextStorageDelegate {
fdb4633d 77
77d0155b
RBR
78 override func textStorageDidProcessEditing(_ obj: Notification) {
79 if let textStorage = obj.object as? NSTextStorage {
fdb4633d 80 self.colorizeText(textStorage: textStorage)
77d0155b
RBR
81 }
82 }
83
84 private func colorizeText(textStorage: NSTextStorage) {
85 let range = NSMakeRange(0, textStorage.length)
86 var matches = vertexRegex.matches(in: textStorage.string, options: [], range: range)
77d0155b
RBR
87
88 for match in matches {
fdb4633d
RBR
89 textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
90 textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
91 textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
92 textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 4))
77d0155b
RBR
93 }
94
95 matches = edgeRegex.matches(in: textStorage.string, options: [], range: range)
96
97 for match in matches {
fdb4633d 98 textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 1))
77d0155b
RBR
99 let arrowRange = match.range(at: 2)
100 textStorage.addAttributes(
fdb4633d 101 [.foregroundColor: NSColor.syntax.symbol],
77d0155b 102 range: NSMakeRange(arrowRange.lowerBound - 1, arrowRange.length + 1))
fdb4633d 103 textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 3))
77d0155b
RBR
104 }
105
106 matches = opportunityRegex.matches(in: textStorage.string, options: [], range: range)
107
108 for match in matches {
fdb4633d
RBR
109 textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
110 textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
111 textStorage.addAttributes([.foregroundColor: NSColor.syntax.symbol], range: match.range(at: 3))
112 textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 4))
77d0155b
RBR
113 }
114
115 matches = blockerRegex.matches(in: textStorage.string, options: [], range: range)
116
117 for match in matches {
fdb4633d
RBR
118 textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
119 textStorage.addAttributes([.foregroundColor: NSColor.syntax.vertex], range: match.range(at: 2))
120 }
121
122 matches = noteRegex.matches(in: textStorage.string, options: [], range: range)
123
124 for match in matches {
125 textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
126 textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
127 textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 3))
77d0155b
RBR
128 }
129
130 matches = stageRegex.matches(in: textStorage.string, options: [], range: range)
131
132 for match in matches {
fdb4633d
RBR
133 textStorage.addAttributes([.foregroundColor: NSColor.syntax.option], range: match.range(at: 1))
134 textStorage.addAttributes([.foregroundColor: NSColor.syntax.number], range: match.range(at: 2))
77d0155b
RBR
135 }
136 }
137}
138
5e8ff485
RBR
139struct MapTextEditor: NSViewControllerRepresentable {
140
141 @Binding var text: String
fdb4633d 142 var onChange: () -> Void = {}
5e8ff485
RBR
143
144 func makeNSViewController(
145 context: NSViewControllerRepresentableContext<MapTextEditor>
146 ) -> MapTextEditorController {
fdb4633d 147 return MapTextEditorController(text: $text, onChange: onChange)
5e8ff485
RBR
148 }
149
150 func updateNSViewController(
151 _ nsViewController: MapTextEditorController,
152 context: NSViewControllerRepresentableContext<MapTextEditor>
fdb4633d 153 ) {}
5e8ff485 154}